﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using System.IO;
using Model;

public static class Server
{
    private const string TEXT_MODE = "Original";
    public const string DEFAULT_EMLAAEI = "ar.emlaaei";
    public const string DEFAULT_TRANSLATION = "en.wordbyword";
    public const string DEFAULT_TRANSLITERATION = "en.transliteration";
    public const string DEFAULT_RECITATION = "Alafasy_64kbps";
    private const string SIMPLIFICATION_SYSTEMS_FOLDER = "Rules";
    private const string STATISTICS_FOLDER = "Statistics";

    private static SimplificationSystem s_simplification_system = new SimplificationSystem();
    public static SimplificationSystem SimplificationSystem
    {
        get { return s_simplification_system; }
    }

    //private static Book book = null;
    public static Book BuildSimplifiedBook()
    {
        // load simplification rules
        LoadSimplificationSystem();

        // load original Quran text and simplify it
        return (Data.BuildSimplifiedBook(Server.SimplificationSystem));
    }

    private static void LoadSimplificationSystem()
    {
        if (s_simplification_system != null)
        {
            string filename = SIMPLIFICATION_SYSTEMS_FOLDER + "/" + TEXT_MODE + ".txt";
            if (File.Exists(filename))
            {
                List<string> lines = Data.LoadLines(filename);

                s_simplification_system.Rules.Clear();
                foreach (string line in lines)
                {
                    string[] parts = line.Split('\t');
                    if (parts.Length == 2)
                    {
                        SimplificationSystem.Rule rule = new SimplificationSystem.Rule(parts[0], parts[1]);
                        s_simplification_system.Rules.Add(rule);
                    }
                    else
                    {
                        throw new Exception(filename + " file format must be:\r\n\tText TAB Replacement");
                    }
                }
            }
        }
    }

    // helper methods for finds
    public static List<Verse> GetSourceVerses(Book book, FindScope find_scope, Selection current_selection, List<Verse> previous_result)
    {
        List<Verse> result = new List<Verse>();
        if (book != null)
        {
            if (find_scope == FindScope.Book)
            {
                result = book.Verses;
            }
            else if (find_scope == FindScope.Selection)
            {
                result = current_selection.Verses;
            }
            else if (find_scope == FindScope.SearchResult)
            {
                if (previous_result != null)
                {
                    result = new List<Verse>(previous_result);
                }
            }
        }
        return result;
    }
    public static List<Verse> GetVerses(List<Phrase> phrases)
    {
        List<Verse> result = new List<Verse>();
        foreach (Phrase phrase in phrases)
        {
            if (!result.Contains(phrase.Verse))
            {
                result.Add(phrase.Verse);
            }
        }
        return result;
    }
    public static List<Phrase> BuildPhrases(Verse verse, MatchCollection matches)
    {
        List<Phrase> result = new List<Phrase>();
        foreach (Match match in matches)
        {
            foreach (Capture capture in match.Captures)
            {
                Phrase phrase = new Phrase(verse, capture.Value, capture.Index);
                result.Add(phrase);
            }
        }
        return result;
    }

    // find by text
    public static List<Phrase> FindPhrases(Book book, FindScope find_scope, Selection current_selection, List<Verse> previous_result, string text, FindByTextLanguageType language_type, string translation, FindByTextLocation text_location, bool case_sensitive, FindByTextWordness wordness, int multiplicity, bool anywhere_in_word)
    {
        List<Phrase> result = new List<Phrase>();

        if (language_type == FindByTextLanguageType.Arabic)
        {
            result = DoFindPhrases(book, find_scope, current_selection, previous_result, text, language_type, translation, text_location, case_sensitive, wordness, multiplicity, anywhere_in_word, true);
        }
        else if (language_type == FindByTextLanguageType.Translation)
        {
            if (book.Verses.Count > 0)
            {
                foreach (string key in book.Verses[0].Translations.Keys)
                {
                    List<Phrase> new_phrases = DoFindPhrases(book, find_scope, current_selection, previous_result, text, language_type, key, text_location, case_sensitive, wordness, multiplicity, anywhere_in_word, false);
                    CollectPhrasesWithoutDuplication(ref result, new_phrases);
                }
            }
        }
        return result;
    }
    private static List<Phrase> DoFindPhrases(Book book, FindScope find_scope, Selection current_selection, List<Verse> previous_result, string text, FindByTextLanguageType language_type, string translation, FindByTextLocation text_location, bool case_sensitive, FindByTextWordness wordness, int multiplicity, bool anywhere_in_word, bool try_emlaaei_if_nothing_found)
    {
        List<Verse> source = GetSourceVerses(book, find_scope, current_selection, previous_result);
        if (language_type == FindByTextLanguageType.Arabic)
        {
            return DoFindPhrases(source, find_scope, current_selection, previous_result, text, text_location, wordness, multiplicity, anywhere_in_word, try_emlaaei_if_nothing_found);
        }
        else //if (language_type == FindByTextLanguageType.Translation)
        {
            return DoFindPhrases(translation, source, find_scope, current_selection, previous_result, text, text_location, case_sensitive, wordness, multiplicity, anywhere_in_word);
        }
    }
    private static List<Phrase> DoFindPhrases(List<Verse> source, FindScope find_scope, Selection current_selection, List<Verse> previous_result, string text, FindByTextLocation text_location, FindByTextWordness wordness, int multiplicity, bool anywhere_in_word, bool try_emlaaei_if_nothing_found)
    {
        List<Phrase> result = new List<Phrase>();
        List<Verse> found_verses = new List<Verse>();
        if (source != null)
        {
            if (source.Count > 0)
            {
                Book book = source[0].Book;
                if (text.Length > 0)
                {
                    /*
                    =====================================================================
                    Regular Expressions (RegEx)
                    =====================================================================
                    Best Reference: http://www.regular-expressions.info/
                    =====================================================================
                    Matches	Characters 
                    x	character x 
                    \\	backslash character 
                    \0n	character with octal value 0n (0 <= n <= 7) 
                    \0nn	character with octal value 0nn (0 <= n <= 7) 
                    \0mnn	character with octal value 0mnn (0 <= m <= 3, 0 <= n <= 7) 
                    \xhh	character with hexadecimal value 0xhh 
                    \uhhhh	character with hexadecimal value 0xhhhh 
                    \t	tab character ('\u0009') 
                    \n	newline (line feed) character ('\u000A') 
                    \r	carriage-return character ('\u000D') 
                    \f	form-feed character ('\u000C') 
                    \a	alert (bell) character ('\u0007') 
                    \e	escape character ('\u001B') 
                    \cx	control character corresponding to x 
                                  
                    Character Classes 
                    [abc]		    a, b, or c				                    (simple class) 
                    [^abc]		    any character except a, b, or c		        (negation) 
                    [a-zA-Z]	    a through z or A through Z, inclusive	    (range) 
                    [a-d[m-p]]	    a through d, or m through p: [a-dm-p]	    (union) 
                    [a-z&&[def]]	d, e, or f				                    (intersection) 
                    [a-z&&[^bc]]	a through z, except for b and c: [ad-z]	    (subtraction) 
                    [a-z&&[^m-p]]	a through z, and not m through p: [a-lq-z]  (subtraction) 
                                  
                    Predefined 
                    .	any character (inc line terminators) except newline 
                    \d	digit				            [0-9] 
                    \D	non-digit			            [^0-9] 
                    \s	whitespace character		    [ \t\n\x0B\f\r] 
                    \S	non-whitespace character	    [^\s] 
                    \w	word character (alphanumeric)	[a-zA-Z_0-9] 
                    \W	non-word character		        [^\w] 

                    Boundary Matchers 
                    ^	beginning of a line	(in Multiline)
                    $	end of a line  		(in Multiline)
                    \b	word boundary 
                    \B	non-word boundary 
                    \A	beginning of the input 
                    \G	end of the previous match 
                    \Z	end of the input but for the final terminator, if any 
                    \z	end of the input

                    Greedy quantifiers 
                    X?	X, once or not at all 
                    X*	X, zero or more times 
                    X+	X, one or more times 
                    X{n}	X, exactly n times 
                    X{n,}	X, at least n times 
                    X{n,m}	X, at least n but not more than m times 
                                  
                    Reluctant quantifiers 
                    X??	X, once or not at all 
                    X*?	X, zero or more times 
                    X+?	X, one or more times 
                    X{n}?	X, exactly n times 
                    X{n,}?	X, at least n times 
                    X{n,m}?	X, at least n but not more than m times 
                                  
                    Possessive quantifiers 
                    X?+	X, once or not at all 
                    X*+	X, zero or more times 
                    X++	X, one or more times 
                    X{n}+	X, exactly n times 
                    X{n,}+	X, at least n times 
                    X{n,m}+	X, at least n but not more than m times 

                    positive lookahead	(?=text)
                    negative lookahead	(?!text)
                    // eg: not at end of line 	    (?!$)
                    positive lookbehind	(?<=text)
                    negative lookbehind	(?<!text)
                    // eg: not at start of line 	(?<!^)
                    =====================================================================
                    */

                    RegexOptions regex_options = RegexOptions.IgnoreCase;
                    regex_options |= RegexOptions.RightToLeft;

                    try
                    {
                        string pattern_empty_line = @"^$";
                        string pattern_whole_line = "(" + @"^" + text + @"$" + ")";

                        string pattern_any_with_prefix = "(" + @"\S+?" + text + ")";
                        string pattern_any_with_prefix_and_suffix = "(" + @"\S+?" + text + @"\S+?" + ")";
                        string pattern_any_with_suffix = "(" + text + @"\S+?" + ")";

                        string pattern_word_with_prefix = "(" + pattern_any_with_prefix + @"\b" + ")";
                        string pattern_word_with_prefix_and_suffix = "(" + pattern_any_with_prefix_and_suffix + ")";
                        string pattern_word_with_suffix = "(" + @"\b" + pattern_any_with_suffix + ")";
                        string pattern_word_with_any_fixes = "(" + pattern_word_with_prefix + "|" + pattern_word_with_prefix_and_suffix + "|" + pattern_any_with_suffix + ")";

                        // Whole word
                        string pattern_whole_word_at_start = "(" + pattern_whole_line + "|" + @"^" + text + @"\b" + ")";
                        string pattern_whole_word_at_middle = "(" + pattern_whole_line + "|" + @"(?<!^)" + @"\b" + text + @"\b" + @"(?!$)" + ")";
                        string pattern_whole_word_at_end = "(" + pattern_whole_line + "|" + @"\b" + text + @"$" + ")";
                        string pattern_whole_word_anywhere = "(" + pattern_whole_line + "|" + @"\b" + text + @"\b" + ")";

                        // Part of word
                        string pattern_part_word_at_start = "(" + @"^" + pattern_word_with_any_fixes + ")";
                        string pattern_part_word_at_middle = "(" + @"(?<!^)" + pattern_word_with_any_fixes + @"(?!$)" + ")";
                        string pattern_part_word_at_end = "(" + pattern_word_with_any_fixes + @"$" + ")";
                        string pattern_part_word_anywhere = "(" + pattern_part_word_at_start + "|" + pattern_part_word_at_middle + "|" + pattern_part_word_at_end + ")";

                        // Any == Whole word | Part of word
                        string pattern_any_at_start = "(" + pattern_whole_line + "|" + @"^" + text + ")";
                        string pattern_any_at_middle = "(" + pattern_whole_line + "|" + @"(?<!^)" + text + @"(?!$)" + ")";
                        string pattern_any_at_end = "(" + pattern_whole_line + "|" + text + @"$" + ")";
                        string pattern_any_anywhere = text;

                        string pattern = "";
                        List<string> negative_words = new List<string>();
                        List<string> positive_words = new List<string>();
                        List<string> unsigned_words = new List<string>();

                        bool at_word_start = !anywhere_in_word;
                        if (at_word_start)
                        {
                            pattern = @"(?<=\b)(" + pattern + @")"; // positive lookbehind
                        }

                        switch (text_location)
                        {
                            case FindByTextLocation.Anywhere:
                                {
                                    if (wordness == FindByTextWordness.WholeWord)
                                    {
                                        pattern += pattern_whole_word_anywhere;
                                    }
                                    else if (wordness == FindByTextWordness.PartOfWord)
                                    {
                                        pattern += pattern_part_word_anywhere;
                                    }
                                    else if (wordness == FindByTextWordness.Any)
                                    {
                                        pattern += pattern_any_anywhere;
                                    }
                                    else
                                    {
                                        pattern += pattern_empty_line;
                                    }
                                }
                                break;
                            case FindByTextLocation.AtStart:
                                {
                                    if (wordness == FindByTextWordness.WholeWord)
                                    {
                                        pattern += pattern_whole_word_at_start;
                                    }
                                    else if (wordness == FindByTextWordness.PartOfWord)
                                    {
                                        pattern += pattern_part_word_at_start;
                                    }
                                    else if (wordness == FindByTextWordness.Any)
                                    {
                                        pattern += pattern_any_at_start;
                                    }
                                    else
                                    {
                                        pattern += pattern_empty_line;
                                    }
                                }
                                break;
                            case FindByTextLocation.AtMiddle:
                                {
                                    if (wordness == FindByTextWordness.WholeWord)
                                    {
                                        pattern += pattern_whole_word_at_middle;
                                    }
                                    else if (wordness == FindByTextWordness.PartOfWord)
                                    {
                                        pattern += pattern_part_word_at_middle;
                                    }
                                    else if (wordness == FindByTextWordness.Any)
                                    {
                                        pattern += pattern_any_at_middle;
                                    }
                                    else
                                    {
                                        pattern += pattern_empty_line;
                                    }
                                }
                                break;
                            case FindByTextLocation.AtEnd:
                                {
                                    if (wordness == FindByTextWordness.WholeWord)
                                    {
                                        pattern += pattern_whole_word_at_end;
                                    }
                                    else if (wordness == FindByTextWordness.PartOfWord)
                                    {
                                        pattern += pattern_part_word_at_end;
                                    }
                                    else if (wordness == FindByTextWordness.Any)
                                    {
                                        pattern += pattern_any_at_end;
                                    }
                                    else
                                    {
                                        pattern += pattern_empty_line;
                                    }
                                }
                                break;
                            case FindByTextLocation.AllWords:
                            case FindByTextLocation.AnyWord:
                                {
                                    pattern = Regex.Replace(text.Trim(), @"\s+", " "); // remove double space or higher if any

                                    string[] pattern_words = pattern.Split(' ');
                                    foreach (string pattern_word in pattern_words)
                                    {
                                        if (pattern_word.StartsWith("-"))
                                        {
                                            negative_words.Add(pattern_word.Substring(1));
                                        }
                                        else if (pattern_word.EndsWith("-"))
                                        {
                                            negative_words.Add(pattern_word.Substring(0, pattern_word.Length - 1));
                                        }
                                        else if (pattern_word.StartsWith("+"))
                                        {
                                            positive_words.Add(pattern_word.Substring(1));
                                        }
                                        else if (pattern_word.EndsWith("+"))
                                        {
                                            positive_words.Add(pattern_word.Substring(0, pattern_word.Length - 1));
                                        }
                                        else
                                        {
                                            unsigned_words.Add(pattern_word);
                                        }
                                    }
                                }
                                break;
                            default:
                                {
                                    return new List<Phrase>();
                                }
                        }

                        // do actual search
                        foreach (Verse verse in source)
                        {
                            /////////////////////////
                            // process negative_words
                            /////////////////////////
                            if (negative_words.Count > 0)
                            {
                                bool skip = false;
                                foreach (string negative_word in negative_words)
                                {
                                    foreach (Word word in verse.Words)
                                    {
                                        // if any negative_word found, skip verse
                                        if (word.Text.Contains(negative_word))
                                        {
                                            skip = true; // next verse
                                            break;
                                        }
                                    }
                                    if (skip)
                                    {
                                        break;
                                    }
                                }
                                if (skip) continue; // next verse
                            }

                            /////////////////////////
                            // process positive_words
                            /////////////////////////
                            if (positive_words.Count > 0)
                            {
                                int matches = 0;
                                foreach (string positive_word in positive_words)
                                {
                                    foreach (Word word in verse.Words)
                                    {
                                        if (word.Text.Contains(positive_word))
                                        {
                                            matches++;
                                            break; // next positive_word
                                        }
                                    }
                                }

                                // verse failed test, so skip it
                                if (matches < positive_words.Count)
                                {
                                    continue; // next verse
                                }
                            }

                            //////////////////////////////////////////////////////
                            // both negative and positive conditions have been met
                            //////////////////////////////////////////////////////

                            /////////////////////////
                            // process unsigned_words
                            /////////////////////////
                            //////////////////////////////////////////////////////////
                            // FindByText WORDS All
                            //////////////////////////////////////////////////////////
                            if (text_location == FindByTextLocation.AllWords)
                            {
                                int matches = 0;
                                foreach (string unsigned_word in unsigned_words)
                                {
                                    foreach (Word word in verse.Words)
                                    {
                                        if (word.Text.Contains(unsigned_word))
                                        {
                                            matches++;
                                            break; // no need to continue even if there are more matches
                                        }
                                    }
                                }

                                if (matches == unsigned_words.Count)
                                {
                                    ///////////////////////////////////////////////////////////////
                                    // all negative, positive and unsigned conditions have been met
                                    ///////////////////////////////////////////////////////////////

                                    // add positive matches
                                    foreach (string positive_word in positive_words)
                                    {
                                        foreach (Word word in verse.Words)
                                        {
                                            if (word.Text.Contains(positive_word))
                                            {
                                                found_verses.Add(verse);
                                                result.Add(new Phrase(verse, word.Text, word.Position));
                                                //break; // no break in case there are more matches
                                            }
                                        }
                                    }

                                    // add unsigned matches
                                    foreach (string unsigned_word in unsigned_words)
                                    {
                                        foreach (Word word in verse.Words)
                                        {
                                            if (word.Text.Contains(unsigned_word))
                                            {
                                                found_verses.Add(verse);
                                                result.Add(new Phrase(verse, word.Text, word.Position));
                                                //break; // no break in case there are more matches
                                            }
                                        }
                                    }
                                }
                                else // verse failed test, so skip it
                                {
                                    continue; // next verse
                                }
                            }
                            //////////////////////////////////////////////////////////
                            // FindByText WORDS Any
                            //////////////////////////////////////////////////////////
                            else if (text_location == FindByTextLocation.AnyWord)
                            {
                                bool found = false;
                                foreach (string unsigned_word in unsigned_words)
                                {
                                    foreach (Word word in verse.Words)
                                    {
                                        if (word.Text.Contains(unsigned_word))
                                        {
                                            found = true;
                                            break; // next unsigned_word
                                        }
                                    }
                                    if (found)
                                    {
                                        break;
                                    }
                                }

                                if (found) // found 1 unsigned word in verse, which is enough
                                {
                                    ///////////////////////////////////////////////////////////////
                                    // all negative, positive and unsigned conditions have been met
                                    ///////////////////////////////////////////////////////////////

                                    // add positive matches
                                    foreach (string positive_word in positive_words)
                                    {
                                        foreach (Word word in verse.Words)
                                        {
                                            if (word.Text.Contains(positive_word))
                                            {
                                                found_verses.Add(verse);
                                                result.Add(new Phrase(verse, word.Text, word.Position));
                                                //break; // no break in case there are more matches
                                            }
                                        }
                                    }

                                    // add unsigned matches
                                    foreach (string unsigned_word in unsigned_words)
                                    {
                                        foreach (Word word in verse.Words)
                                        {
                                            if (word.Text.Contains(unsigned_word))
                                            {
                                                found_verses.Add(verse);
                                                result.Add(new Phrase(verse, word.Text, word.Position));
                                                //break; // no break in case there are more matches
                                            }
                                        }
                                    }
                                }
                                else // verse failed test, so skip it
                                {
                                    continue; // next verse
                                }
                            }
                            //////////////////////////////////////////////////////////
                            // FindByText EXACT 
                            //////////////////////////////////////////////////////////
                            else // at start, middle, end, or anywhere
                            {
                                MatchCollection matches = Regex.Matches(verse.Text, pattern, regex_options);
                                if (multiplicity != -1) // with multiplicity
                                {
                                    if (matches.Count == multiplicity)
                                    {
                                        found_verses.Add(verse);
                                        if (matches.Count > 0)
                                        {
                                            result.AddRange(BuildPhrases(verse, matches));
                                        }
                                        else
                                        {
                                            result.Add(new Phrase(verse, "", 0));
                                        }
                                    }
                                }
                                else // without multiplicity
                                {
                                    if (matches.Count > 0)
                                    {
                                        found_verses.Add(verse);
                                        result.AddRange(BuildPhrases(verse, matches));
                                    }
                                }
                            }
                        } // end for

                        // if nothing found in original/simplified, try emlaaei
                        if ((multiplicity != 0) && (result.Count == 0))
                        {
                            if (try_emlaaei_if_nothing_found)
                            {
                                if ((source != null) && (source.Count > 0))
                                {
                                    foreach (Verse verse in source)
                                    {
                                        if (text_location == FindByTextLocation.AllWords)
                                        {
                                            bool skip = false;
                                            foreach (string pattern_word in negative_words)
                                            {
                                                if (verse.Translations[DEFAULT_EMLAAEI].Contains(pattern_word))
                                                {
                                                    skip = true;
                                                    break;
                                                }
                                            }
                                            if (skip) continue;

                                            foreach (string pattern_word in positive_words)
                                            {
                                                if (!verse.Translations[DEFAULT_EMLAAEI].Contains(pattern_word))
                                                {
                                                    skip = true;
                                                    break;
                                                }
                                            }
                                            if (skip) continue;

                                            if (
                                                 (unsigned_words.Count == 0) ||
                                                 (verse.Translations[DEFAULT_EMLAAEI].ContainsWordsOf(unsigned_words))
                                               )
                                            {
                                                result.Add(new Phrase(verse, "", 0));
                                            }
                                        }
                                        else if (text_location == FindByTextLocation.AnyWord)
                                        {
                                            bool skip = false;
                                            foreach (string pattern_word in negative_words)
                                            {
                                                if (verse.Translations[DEFAULT_EMLAAEI].Contains(pattern_word))
                                                {
                                                    skip = true;
                                                    break;
                                                }
                                            }
                                            if (skip) continue;

                                            foreach (string pattern_word in positive_words)
                                            {
                                                if (!verse.Translations[DEFAULT_EMLAAEI].Contains(pattern_word))
                                                {
                                                    skip = true;
                                                    break;
                                                }
                                            }
                                            if (skip) continue;

                                            if (
                                                 (negative_words.Count > 0) ||
                                                 (positive_words.Count > 0) ||
                                                 (
                                                   (unsigned_words.Count == 0) ||
                                                   (verse.Translations[DEFAULT_EMLAAEI].ContainsWordOf(unsigned_words))
                                                 )
                                               )
                                            {
                                                result.Add(new Phrase(verse, "", 0));
                                            }
                                        }
                                        else // at start, middle, end, or anywhere
                                        {
                                            MatchCollection matches = Regex.Matches(verse.Translations[DEFAULT_EMLAAEI], pattern, regex_options);
                                            if (multiplicity != -1) // with multiplicity
                                            {
                                                if (matches.Count == multiplicity)
                                                {
                                                    result.AddRange(BuildPhrases(verse, matches));
                                                }
                                            }
                                            else // without multiplicity
                                            {
                                                if (matches.Count > 0)
                                                {
                                                    result.AddRange(BuildPhrases(verse, matches));
                                                }
                                            }
                                        }
                                    } // end for

                                    // update phrase positoins for correct colorizing
                                    if (result != null)
                                    {
                                        if (result.Count > 0)
                                        {
                                            UpdatePhrasePositions(book, ref result, "Simplified29", "Original");
                                        }
                                    }

                                }
                            }
                        }
                    }
                    catch
                    {
                        // log exception
                    }
                }
            }
        }
        return result;
    }
    private static List<Phrase> DoFindPhrases(string translation, List<Verse> source, FindScope find_scope, Selection current_selection, List<Verse> previous_result, string text, FindByTextLocation text_location, bool case_sensitive, FindByTextWordness wordness, int multiplicity, bool anywhere_in_word)
    {
        List<Phrase> result = new List<Phrase>();
        List<Verse> found_verses = new List<Verse>();
        if (source != null)
        {
            if (source.Count > 0)
            {
                Book book = source[0].Book;
                if (text.Length > 0)
                {
                    RegexOptions regex_options = case_sensitive ? RegexOptions.None : RegexOptions.IgnoreCase;
                    if (text.IsArabic()) // Arabic letters in translation (Emlaaei, Urdu, Farsi, etc.) 
                    {
                        regex_options |= RegexOptions.RightToLeft;
                    }

                    try
                    {
                        string pattern_empty_line = @"^$";
                        string pattern_whole_line = "(" + @"^" + text + @"$" + ")";

                        string pattern_any_with_prefix = "(" + @"\S+?" + text + ")";
                        string pattern_any_with_prefix_and_suffix = "(" + @"\S+?" + text + @"\S+?" + ")";
                        string pattern_any_with_suffix = "(" + text + @"\S+?" + ")";

                        string pattern_word_with_prefix = "(" + pattern_any_with_prefix + @"\b" + ")";
                        string pattern_word_with_prefix_and_suffix = "(" + pattern_any_with_prefix_and_suffix + ")";
                        string pattern_word_with_suffix = "(" + @"\b" + pattern_any_with_suffix + ")";
                        string pattern_word_with_any_fixes = "(" + pattern_word_with_prefix + "|" + pattern_word_with_prefix_and_suffix + "|" + pattern_any_with_suffix + ")";

                        // Whole word
                        string pattern_whole_word_at_start = "(" + pattern_whole_line + "|" + @"^" + text + @"\b" + ")";
                        string pattern_whole_word_at_middle = "(" + pattern_whole_line + "|" + @"(?<!^)" + @"\b" + text + @"\b" + @"(?!$)" + ")";
                        string pattern_whole_word_at_end = "(" + pattern_whole_line + "|" + @"\b" + text + @"$" + ")";
                        string pattern_whole_word_anywhere = "(" + pattern_whole_line + "|" + @"\b" + text + @"\b" + ")";

                        // Part of word
                        string pattern_part_word_at_start = "(" + @"^" + pattern_word_with_any_fixes + ")";
                        string pattern_part_word_at_middle = "(" + @"(?<!^)" + pattern_word_with_any_fixes + @"(?!$)" + ")";
                        string pattern_part_word_at_end = "(" + pattern_word_with_any_fixes + @"$" + ")";
                        string pattern_part_word_anywhere = "(" + pattern_part_word_at_start + "|" + pattern_part_word_at_middle + "|" + pattern_part_word_at_end + ")";

                        // Any == Whole word | Part of word
                        string pattern_any_at_start = "(" + pattern_whole_line + "|" + @"^" + text + ")";
                        string pattern_any_at_middle = "(" + pattern_whole_line + "|" + @"(?<!^)" + text + @"(?!$)" + ")";
                        string pattern_any_at_end = "(" + pattern_whole_line + "|" + text + @"$" + ")";
                        string pattern_any_anywhere = text;

                        string pattern = "";
                        List<string> negative_words = new List<string>();
                        List<string> positive_words = new List<string>();
                        List<string> unsigned_words = new List<string>();

                        bool at_word_start = !anywhere_in_word;
                        if (at_word_start)
                        {
                            pattern = @"(?<=\b)(" + pattern + @")"; // positive lookbehind
                        }

                        switch (text_location)
                        {
                            case FindByTextLocation.Anywhere:
                                {
                                    if (wordness == FindByTextWordness.WholeWord)
                                    {
                                        pattern += pattern_whole_word_anywhere;
                                    }
                                    else if (wordness == FindByTextWordness.PartOfWord)
                                    {
                                        pattern += pattern_part_word_anywhere;
                                    }
                                    else if (wordness == FindByTextWordness.Any)
                                    {
                                        pattern += pattern_any_anywhere;
                                    }
                                    else
                                    {
                                        pattern += pattern_empty_line;
                                    }
                                }
                                break;
                            case FindByTextLocation.AtStart:
                                {
                                    if (wordness == FindByTextWordness.WholeWord)
                                    {
                                        pattern += pattern_whole_word_at_start;
                                    }
                                    else if (wordness == FindByTextWordness.PartOfWord)
                                    {
                                        pattern += pattern_part_word_at_start;
                                    }
                                    else if (wordness == FindByTextWordness.Any)
                                    {
                                        pattern += pattern_any_at_start;
                                    }
                                    else
                                    {
                                        pattern += pattern_empty_line;
                                    }
                                }
                                break;
                            case FindByTextLocation.AtMiddle:
                                {
                                    if (wordness == FindByTextWordness.WholeWord)
                                    {
                                        pattern += pattern_whole_word_at_middle;
                                    }
                                    else if (wordness == FindByTextWordness.PartOfWord)
                                    {
                                        pattern += pattern_part_word_at_middle;
                                    }
                                    else if (wordness == FindByTextWordness.Any)
                                    {
                                        pattern += pattern_any_at_middle;
                                    }
                                    else
                                    {
                                        pattern += pattern_empty_line;
                                    }
                                }
                                break;
                            case FindByTextLocation.AtEnd:
                                {
                                    if (wordness == FindByTextWordness.WholeWord)
                                    {
                                        pattern += pattern_whole_word_at_end;
                                    }
                                    else if (wordness == FindByTextWordness.PartOfWord)
                                    {
                                        pattern += pattern_part_word_at_end;
                                    }
                                    else if (wordness == FindByTextWordness.Any)
                                    {
                                        pattern += pattern_any_at_end;
                                    }
                                    else
                                    {
                                        pattern += pattern_empty_line;
                                    }
                                }
                                break;
                            case FindByTextLocation.AllWords:
                            case FindByTextLocation.AnyWord:
                                {
                                    pattern = Regex.Replace(text.Trim(), @"\s+", " "); // remove double space or higher if any

                                    string[] pattern_words = pattern.Split(' ');
                                    foreach (string pattern_word in pattern_words)
                                    {
                                        if (pattern_word.StartsWith("-"))
                                        {
                                            negative_words.Add(pattern_word.Substring(1));
                                        }
                                        else if (pattern_word.EndsWith("-"))
                                        {
                                            negative_words.Add(pattern_word.Substring(0, pattern_word.Length - 1));
                                        }
                                        else if (pattern_word.StartsWith("+"))
                                        {
                                            positive_words.Add(pattern_word.Substring(1));
                                        }
                                        else if (pattern_word.EndsWith("+"))
                                        {
                                            positive_words.Add(pattern_word.Substring(0, pattern_word.Length - 1));
                                        }
                                        else
                                        {
                                            unsigned_words.Add(pattern_word);
                                        }
                                    }
                                }
                                break;
                            default:
                                {
                                    return new List<Phrase>();
                                }
                        }

                        // do actual search
                        foreach (Verse verse in source)
                        {
                            if (text_location == FindByTextLocation.AllWords)
                            {
                                bool skip = false;
                                foreach (string negative_word in negative_words)
                                {
                                    if (verse.Translations[translation].Contains(negative_word))
                                    {
                                        skip = true;
                                        break;
                                    }
                                }
                                if (skip) continue;

                                foreach (string positive_word in positive_words)
                                {
                                    if (!verse.Translations[translation].Contains(positive_word))
                                    {
                                        skip = true;
                                        break;
                                    }
                                }
                                if (skip) continue;

                                if (
                                     (unsigned_words.Count == 0) ||
                                     (verse.Translations[translation].ContainsWordsOf(unsigned_words))
                                   )
                                {
                                    found_verses.Add(verse);
                                    result.Add(new Phrase(verse, "", 0));
                                }
                            }
                            else if (text_location == FindByTextLocation.AnyWord)
                            {
                                bool skip = false;
                                foreach (string negative_word in negative_words)
                                {
                                    if (verse.Translations[translation].Contains(negative_word))
                                    {
                                        skip = true;
                                        break;
                                    }
                                }
                                if (skip) continue;

                                foreach (string positive_word in positive_words)
                                {
                                    if (!verse.Translations[translation].Contains(positive_word))
                                    {
                                        skip = true;
                                        break;
                                    }
                                }
                                if (skip) continue;

                                if (
                                     (negative_words.Count > 0) ||
                                     (positive_words.Count > 0) ||
                                     (
                                       (unsigned_words.Count == 0) ||
                                       (verse.Translations[translation].ContainsWordOf(unsigned_words))
                                     )
                                   )
                                {
                                    found_verses.Add(verse);
                                    result.Add(new Phrase(verse, "", 0));
                                }
                            }
                            else // at start, middle, end, or anywhere
                            {
                                MatchCollection matches = Regex.Matches(verse.Translations[translation], pattern, regex_options);
                                if (multiplicity != -1) // with multiplicity
                                {
                                    if (matches.Count == multiplicity)
                                    {
                                        found_verses.Add(verse);
                                        if (matches.Count > 0)
                                        {
                                            result.AddRange(BuildPhrases(verse, matches));
                                        }
                                        else
                                        {
                                            result.Add(new Phrase(verse, "", 0));
                                        }
                                    }
                                }
                                else // without multiplicity
                                {
                                    if (matches.Count > 0)
                                    {
                                        found_verses.Add(verse);
                                        result.AddRange(BuildPhrases(verse, matches));
                                    }
                                }
                            }
                        } // end for
                    }
                    catch
                    {
                        // log exception
                    }
                }
            }
        }
        return result;
    }
    private static void CollectPhrasesWithoutDuplication(ref List<Phrase> old_phrases, List<Phrase> new_phrases)
    {
        if (old_phrases == null) old_phrases = new List<Phrase>();
        if (new_phrases == null) return;

        List<Phrase> temp = new List<Phrase>();
        foreach (Phrase new_phrase in new_phrases)
        {
            bool already_exist = false;
            foreach (Phrase old_phrase in old_phrases)
            {
                if (old_phrase.Verse.Number == new_phrase.Verse.Number)
                {
                    already_exist = true; // don't add
                    break;
                }
            }

            if (!already_exist)
            {
                temp.Add(new_phrase);
            }
        }

        old_phrases.AddRange(temp);
    }
    public static void UpdatePhrasePositions(Book book, ref List<Phrase> phrases, string from_text_mode, string to_text_mode)
    {
        if (from_text_mode == to_text_mode) return;

        // NOTE: Original text mode still not working correctly. It finds the positions but uses the simplified length which is too short
        // letter position stays the same except for Simplified28 and Simplified29Shadda
        if (phrases != null)
        {
            if (phrases.Count > 0)
            {
                List<Phrase> temp = new List<Phrase>();
                for (int i = 0; i < phrases.Count; i++)
                {
                    Verse phrase_verse = phrases[i].Verse;
                    string phrase_text = phrases[i].Text;
                    int phrase_position = phrases[i].Position;

                    Verse verse = book.Verses[phrase_verse.Number - 1];
                    string text = Server.SimplificationSystem.SimplifyText(phrase_text);

                    // FIX: double/half word length between Original and SimplifiedXX for now.
                    if (to_text_mode == "Original")
                    {
                        text += text;
                    }
                    else // to SimplifiedXX
                    {
                        if (from_text_mode == "Original")
                        {
                            int index = text.Length / 2;
                            if ((index >= 0) && (index < text.Length))
                            {
                                text = text.Remove(index);
                            }
                        }
                    }

                    int position = -1;
                    int spaces = 0;
                    int letters = 0;
                    if (from_text_mode == "Original")
                    {
                        MatchCollection matches = Regex.Matches(verse.Text, text);
                        foreach (Match match in matches)
                        {
                            position = match.Index;
                            Phrase phrase = new Phrase(verse, text, position);
                            temp.Add(phrase);
                        }
                    }
                    else if (to_text_mode == "Original")
                    {
                        for (int j = 0; j < verse.Text.Length; j++)
                        {
                            if (verse.Text[j] == ' ')
                            {
                                if (j > 2)
                                {
                                    // don't add space for stop_mark words
                                    char previous_previous_letter = verse.Text[j - 2];
                                    if (previous_previous_letter == ' ')
                                    {
                                        char previous_letter = verse.Text[j - 1];
                                        if ((previous_letter == 'ص') || (previous_letter == 'ق') || (previous_letter == 'ن') || (previous_letter == 'و'))
                                        {
                                            spaces++;
                                        }
                                    }
                                    else
                                    {
                                        spaces++;
                                    }
                                }
                                else
                                {
                                    spaces++;
                                }
                            }
                            else if (Constants.ARABIC_LETTERS.Contains(verse.Text[j]))
                            {
                                letters++;

                                if ((letters + spaces) > phrase_position)
                                {
                                    position = j;
                                    break;
                                }
                            }
                        }
                        Phrase phrase = new Phrase(verse, text, position);
                        temp.Add(phrase);
                    }
                    else
                    {
                        position = phrase_position;
                        Phrase phrase = new Phrase(verse, text, position);
                        temp.Add(phrase);
                    }
                }

                phrases = temp;
            }
        }
    }

    // find by roots
    public static List<Phrase> FindPhrases(Book book, FindScope find_scope, Selection current_selection, List<Verse> previous_result, string roots, int multiplicity)
    {
        List<Phrase> result = new List<Phrase>();
        List<Verse> found_verses = null;

        while (roots.Contains("  "))
        {
            roots = roots.Replace("  ", " ");
        }

        string[] parts = roots.Split(' ');
        if (parts.Length == 0)
        {
            return result;
        }
        else if (parts.Length == 1)
        {
            return DoFindPhrases(book, find_scope, current_selection, previous_result, roots, multiplicity);
        }
        else if (parts.Length > 1) // enable nested searches
        {
            if (roots.Length > 1) // enable nested searches
            {
                List<Phrase> phrases = null;
                List<Verse> verses = null;

                foreach (string part in parts)
                {
                    if (part.StartsWith("-"))
                    {
                        phrases = DoFindPhrases(book, find_scope, current_selection, found_verses, part.Remove(0, 1), 0); // multiplicity = 0 for exclude
                    }
                    else if (part.StartsWith("+"))
                    {
                        phrases = DoFindPhrases(book, find_scope, current_selection, found_verses, part.Remove(0, 1), multiplicity);
                    }
                    else
                    {
                        phrases = DoFindPhrases(book, find_scope, current_selection, found_verses, part, multiplicity);
                    }
                    verses = new List<Verse>(GetVerses(phrases));

                    // if first result
                    if (found_verses == null)
                    {
                        // fill it up with a copy of the first root search result
                        result = new List<Phrase>(phrases);
                        found_verses = new List<Verse>(verses);

                        // prepare for nested search by search
                        find_scope = FindScope.SearchResult;
                    }
                    else // subsequent search result
                    {
                        found_verses = new List<Verse>(verses);

                        List<Phrase> union_phrases = new List<Phrase>(phrases);
                        foreach (Phrase phrase in result)
                        {
                            if (verses.Contains(phrase.Verse))
                            {
                                union_phrases.Add(phrase);
                            }
                        }
                        result = union_phrases;
                    }
                }
            }
        }
        return result;
    }
    private static List<Phrase> DoFindPhrases(Book book, FindScope find_scope, Selection current_selection, List<Verse> previous_result, string root, int multiplicity)
    {
        List<Verse> source = GetSourceVerses(book, find_scope, current_selection, previous_result);
        return DoFindPhrases(source, find_scope, current_selection, previous_result, root, multiplicity);
    }
    private static List<Phrase> DoFindPhrases(List<Verse> source, FindScope find_scope, Selection current_selection, List<Verse> previous_result, string root, int multiplicity)
    {
        List<Phrase> result = new List<Phrase>();
        List<Verse> found_verses = new List<Verse>();
        if (source != null)
        {
            if (source.Count > 0)
            {
                Book book = source[0].Book;
                if (root.Length > 0)
                {
                    try
                    {
                        Dictionary<string, List<Word>> root_words_dictionary = book.RootWords;
                        if (root_words_dictionary != null)
                        {
                            List<Word> root_words = null;
                            if (root_words_dictionary.ContainsKey(root))
                            {
                                // get all pre-identified root_words
                                root_words = root_words_dictionary[root];
                            }
                            else // if no such root, search for the matching root_word by its verse position and get its root and then get all root_words
                            {
                                string new_root = book.GetRoot(root);
                                if (!String.IsNullOrEmpty(new_root))
                                {
                                    // get all pre-identified root_words for new root
                                    root_words = root_words_dictionary[new_root];
                                }
                            }

                            if (root_words != null)
                            {
                                result = GetPhrasesWithRootWords(source, root_words, multiplicity);
                                foreach (Phrase phrase in result)
                                {
                                    if (!found_verses.Contains(phrase.Verse))
                                    {
                                        found_verses.Add(phrase.Verse);
                                    }
                                }
                            }
                        }
                    }
                    catch
                    {
                        // log exception
                    }
                }
            }
        }
        return result;
    }
    private static List<Phrase> GetPhrasesWithRootWords(List<Verse> source, List<Word> root_words, int multiplicity)
    {
        List<Phrase> result = new List<Phrase>();
        if (source != null)
        {
            if (source.Count > 0)
            {
                Book book = source[0].Book;
                Dictionary<Verse, int> multiplicity_dictionary = new Dictionary<Verse, int>();
                foreach (Word word in root_words)
                {
                    Verse verse = book.Verses[word.Verse.Number - 1];
                    if (source.Contains(verse))
                    {
                        if (multiplicity_dictionary.ContainsKey(verse))
                        {
                            multiplicity_dictionary[verse]++;
                        }
                        else // first find
                        {
                            multiplicity_dictionary.Add(verse, 1);
                        }
                    }
                }

                if (multiplicity == 0) // verses not containg word
                {
                    foreach (Verse verse in source)
                    {
                        if (!multiplicity_dictionary.ContainsKey(verse))
                        {
                            Phrase phrase = new Phrase(verse, "", 0);
                            result.Add(phrase);
                        }
                    }
                }
                else // add only matching multiplicity or wildcard (-1)
                {
                    foreach (Word root_word in root_words)
                    {
                        Verse verse = book.Verses[root_word.Verse.Number - 1];
                        if ((multiplicity == -1) || (multiplicity_dictionary[verse] == multiplicity))
                        {
                            if (source.Contains(verse))
                            {
                                int word_index = root_word.NumberInVerse - 1;
                                string word_text = verse.Words[word_index].Text;
                                int word_position = verse.Words[word_index].Position;
                                Phrase phrase = new Phrase(verse, word_text, word_position);
                                result.Add(phrase);
                            }
                        }
                    }
                }
            }
        }
        return result;
    }

    // find by similar words
    public static List<Phrase> FindPhrases(Book book, FindScope find_scope, Selection current_selection, List<Verse> previous_result, string text, double similarity_percentage)
    {
        List<Phrase> result = new List<Phrase>();
        List<Verse> found_verses = null;

        while (text.Contains("  "))
        {
            text = text.Replace("  ", " ");
        }

        string[] word_texts = text.Split(' ');
        if (word_texts.Length == 0)
        {
            return result;
        }
        else if (word_texts.Length == 1)
        {
            return DoFindPhrases(book, find_scope, current_selection, previous_result, text, similarity_percentage);
        }
        else if (word_texts.Length > 1) // enable nested searches
        {
            if (text.Length > 1) // enable nested searches
            {
                List<Phrase> phrases = null;
                List<Verse> verses = null;

                foreach (string word_text in word_texts)
                {
                    phrases = DoFindPhrases(book, find_scope, current_selection, found_verses, word_text, similarity_percentage);
                    verses = new List<Verse>(GetVerses(phrases));

                    // if first result
                    if (found_verses == null)
                    {
                        // fill it up with a copy of the first similar word search result
                        result = new List<Phrase>(phrases);
                        found_verses = new List<Verse>(verses);

                        // prepare for nested search by search
                        find_scope = FindScope.SearchResult;
                    }
                    else // subsequent search result
                    {
                        found_verses = new List<Verse>(verses);

                        List<Phrase> union_phrases = new List<Phrase>(phrases);
                        foreach (Phrase phrase in result)
                        {
                            if (verses.Contains(phrase.Verse))
                            {
                                union_phrases.Add(phrase);
                            }
                        }
                        result = union_phrases;
                    }
                }
            }
        }
        return result;
    }
    private static List<Phrase> DoFindPhrases(Book book, FindScope find_scope, Selection current_selection, List<Verse> previous_result, string word_text, double similarity_percentage)
    {
        List<Verse> source = GetSourceVerses(book, find_scope, current_selection, previous_result);
        return DoFindPhrases(source, find_scope, current_selection, previous_result, word_text, similarity_percentage);
    }
    private static List<Phrase> DoFindPhrases(List<Verse> source, FindScope find_scope, Selection current_selection, List<Verse> previous_result, string word_text, double similarity_percentage)
    {
        List<Phrase> result = new List<Phrase>();
        List<Verse> found_verses = new List<Verse>();
        if (source != null)
        {
            if (source.Count > 0)
            {
                if (!String.IsNullOrEmpty(word_text))
                {
                    Book book = source[0].Book;
                    if (word_text.Length > 0)
                    {
                        try
                        {
                            foreach (Verse verse in source)
                            {
                                foreach (Word word in verse.Words)
                                {
                                    if (word.Text.IsSimilarTo(word_text, similarity_percentage))
                                    {
                                        if (!found_verses.Contains(verse))
                                        {
                                            found_verses.Add(verse);
                                        }
                                        result.Add(new Phrase(verse, word.Text, word.Position));
                                    }
                                }
                            }
                        }
                        catch
                        {
                            // log exception
                        }
                    }
                }
            }
        }
        return result;
    }

    // find by similarity
    public static List<Verse> FindVerses(Book book, FindScope find_scope, Selection current_selection, List<Verse> previous_result, Verse verse, FindBySimilarityMethod similarity_method, double similarity_percentage)
    {
        return DoFindVerses(book, find_scope, current_selection, previous_result, verse, similarity_method, similarity_percentage);
    }
    private static List<Verse> DoFindVerses(Book book, FindScope find_scope, Selection current_selection, List<Verse> previous_result, Verse verse, FindBySimilarityMethod similarity_method, double similarity_percentage)
    {
        List<Verse> source = GetSourceVerses(book, find_scope, current_selection, previous_result);
        return DoFindVerses(source, find_scope, current_selection, previous_result, verse, similarity_method, similarity_percentage);
    }
    private static List<Verse> DoFindVerses(List<Verse> source, FindScope find_scope, Selection current_selection, List<Verse> previous_result, Verse verse, FindBySimilarityMethod find_similarity_method, double similarity_percentage)
    {
        List<Verse> result = new List<Verse>();
        if (source != null)
        {
            if (source.Count > 0)
            {
                Book book = source[0].Book;
                if (verse != null)
                {
                    switch (find_similarity_method)
                    {
                        case FindBySimilarityMethod.SimilarText:
                            {
                                for (int j = 0; j < source.Count; j++)
                                {
                                    if (verse.Text.IsSimilarTo(source[j].Text, similarity_percentage))
                                    {
                                        result.Add(source[j]);
                                    }
                                }
                            }
                            break;
                        case FindBySimilarityMethod.SimilarWords:
                            {
                                for (int j = 0; j < source.Count; j++)
                                {
                                    if (verse.Text.HasSimilarWordsTo(source[j].Text, (int)Math.Round((Math.Min(verse.Words.Count, source[j].Words.Count) * similarity_percentage)), 1.0))
                                    {
                                        result.Add(source[j]);
                                    }
                                }
                            }
                            break;
                        case FindBySimilarityMethod.SimilarFirstWord:
                            {
                                for (int j = 0; j < source.Count; j++)
                                {
                                    if (verse.Text.HasSimilarFirstWordTo(source[j].Text, similarity_percentage))
                                    {
                                        result.Add(source[j]);
                                    }
                                }
                            }
                            break;
                        case FindBySimilarityMethod.SimilarLastWord:
                            {
                                for (int j = 0; j < source.Count; j++)
                                {
                                    if (verse.Text.HasSimilarLastWordTo(source[j].Text, similarity_percentage))
                                    {
                                        result.Add(source[j]);
                                    }
                                }
                            }
                            break;
                        default:
                            break;
                    }
                }
            }
        }
        return result;
    }

    public static List<List<Verse>> FindVerseRanges(Book book, FindScope find_scope, Selection current_selection, List<Verse> previous_result, FindBySimilarityMethod similarity_method, double similarity_percentage)
    {
        return DoFindVerseRanges(book, find_scope, current_selection, previous_result, similarity_method, similarity_percentage);
    }
    private static List<List<Verse>> DoFindVerseRanges(Book book, FindScope find_scope, Selection current_selection, List<Verse> previous_result, FindBySimilarityMethod similarity_method, double similarity_percentage)
    {
        List<Verse> source = GetSourceVerses(book, find_scope, current_selection, previous_result);
        return DoFindVerseRanges(source, find_scope, current_selection, previous_result, similarity_method, similarity_percentage);
    }
    private static List<List<Verse>> DoFindVerseRanges(List<Verse> source, FindScope find_scope, Selection current_selection, List<Verse> previous_result, FindBySimilarityMethod find_similarity_method, double similarity_percentage)
    {
        List<List<Verse>> result = new List<List<Verse>>();
        Dictionary<Verse, List<Verse>> verse_ranges = new Dictionary<Verse, List<Verse>>(); // need dictionary to check if key exist
        bool[] already_compared = new bool[Verse.MAX_NUMBER];
        if (source != null)
        {
            if (source.Count > 0)
            {
                Book book = source[0].Book;
                switch (find_similarity_method)
                {
                    case FindBySimilarityMethod.SimilarText:
                        {
                            for (int i = 0; i < source.Count - 1; i++)
                            {
                                for (int j = i + 1; j < source.Count; j++)
                                {
                                    if (!already_compared[j])
                                    {
                                        if (source[i].Text.IsSimilarTo(source[j].Text, similarity_percentage))
                                        {
                                            if (!verse_ranges.ContainsKey(source[i])) // first time matching verses found
                                            {
                                                List<Verse> similar_verses = new List<Verse>();
                                                verse_ranges.Add(source[i], similar_verses);
                                                similar_verses.Add(source[i]);
                                                similar_verses.Add(source[j]);
                                                already_compared[i] = true;
                                                already_compared[j] = true;
                                            }
                                            else // matching verses already exists
                                            {
                                                List<Verse> similar_verses = verse_ranges[source[i]];
                                                similar_verses.Add(source[j]);
                                                already_compared[j] = true;
                                            }
                                        }
                                    }
                                }
                            }
                        }
                        break;
                    case FindBySimilarityMethod.SimilarWords:
                        {
                            for (int i = 0; i < source.Count - 1; i++)
                            {
                                for (int j = i + 1; j < source.Count; j++)
                                {
                                    if (!already_compared[j])
                                    {
                                        if (source[i].Text.HasSimilarWordsTo(source[j].Text, (int)Math.Round((Math.Min(source[i].Words.Count, source[j].Words.Count) * similarity_percentage)), 1.0))
                                        {
                                            if (!verse_ranges.ContainsKey(source[i])) // first time matching verses found
                                            {
                                                List<Verse> similar_verses = new List<Verse>();
                                                verse_ranges.Add(source[i], similar_verses);
                                                similar_verses.Add(source[i]);
                                                similar_verses.Add(source[j]);
                                                already_compared[i] = true;
                                                already_compared[j] = true;
                                            }
                                            else // matching verses already exists
                                            {
                                                List<Verse> similar_verses = verse_ranges[source[i]];
                                                similar_verses.Add(source[j]);
                                                already_compared[j] = true;
                                            }
                                        }
                                    }
                                }
                            }
                        }
                        break;
                    case FindBySimilarityMethod.SimilarFirstWord:
                        {
                            for (int i = 0; i < source.Count - 1; i++)
                            {
                                for (int j = i + 1; j < source.Count; j++)
                                {
                                    if (!already_compared[j])
                                    {
                                        if (source[j].Text.HasSimilarFirstWordTo(source[j].Text, similarity_percentage))
                                        {
                                            if (!verse_ranges.ContainsKey(source[i])) // first time matching verses found
                                            {
                                                List<Verse> similar_verses = new List<Verse>();
                                                verse_ranges.Add(source[i], similar_verses);
                                                similar_verses.Add(source[i]);
                                                similar_verses.Add(source[j]);
                                                already_compared[i] = true;
                                                already_compared[j] = true;
                                            }
                                            else // matching verses already exists
                                            {
                                                List<Verse> similar_verses = verse_ranges[source[i]];
                                                similar_verses.Add(source[j]);
                                                already_compared[j] = true;
                                            }
                                        }
                                    }
                                }
                            }
                        }
                        break;
                    case FindBySimilarityMethod.SimilarLastWord:
                        {
                            for (int i = 0; i < source.Count - 1; i++)
                            {
                                for (int j = i + 1; j < source.Count; j++)
                                {
                                    if (!already_compared[j])
                                    {
                                        if (source[i].Text.HasSimilarLastWordTo(source[j].Text, similarity_percentage))
                                        {
                                            if (!verse_ranges.ContainsKey(source[i])) // first time matching verses found
                                            {
                                                List<Verse> similar_verses = new List<Verse>();
                                                verse_ranges.Add(source[i], similar_verses);
                                                similar_verses.Add(source[i]);
                                                similar_verses.Add(source[j]);
                                                already_compared[i] = true;
                                                already_compared[j] = true;
                                            }
                                            else // matching verses already exists
                                            {
                                                List<Verse> similar_verses = verse_ranges[source[i]];
                                                similar_verses.Add(source[j]);
                                                already_compared[j] = true;
                                            }
                                        }
                                    }
                                }
                            }
                        }
                        break;
                    default:
                        break;
                }
            }
        }

        // copy dictionary to list of list
        if (verse_ranges.Count > 0)
        {
            foreach (List<Verse> verse_range in verse_ranges.Values)
            {
                result.Add(verse_range);
            }
        }
        return result;
    }

    // find by prostration type
    public static List<Verse> FindVerses(Book book, FindScope find_scope, Selection current_selection, List<Verse> previous_result, FindByProstrationType prostration_type)
    {
        return DoFindVerses(book, find_scope, current_selection, previous_result, prostration_type);
    }
    private static List<Verse> DoFindVerses(Book book, FindScope find_scope, Selection current_selection, List<Verse> previous_result, FindByProstrationType prostration_type)
    {
        List<Verse> source = GetSourceVerses(book, find_scope, current_selection, previous_result);
        return DoFindVerses(source, prostration_type);
    }
    private static List<Verse> DoFindVerses(List<Verse> source, FindByProstrationType prostration_type)
    {
        List<Verse> result = new List<Verse>();
        if (source != null)
        {
            if (source.Count > 0)
            {
                Book book = source[0].Book;
                switch (prostration_type)
                {
                    case FindByProstrationType.None:
                        {
                            // add nothing

                            //foreach (Verse verse in source)
                            //{
                            //    if (verse.Prostration == null)
                            //    {
                            //        s_found_verses.Add(verse);
                            //    }
                            //}
                        }
                        break;
                    case FindByProstrationType.Obligatory:
                        {
                            foreach (Verse verse in source)
                            {
                                if (verse.Prostration != null)
                                {
                                    if (verse.Prostration.Type == ProstrationType.Obligatory)
                                    {
                                        result.Add(verse);
                                    }
                                }
                            }
                        }
                        break;
                    case FindByProstrationType.Recommended:
                        {
                            foreach (Verse verse in source)
                            {
                                if (verse.Prostration != null)
                                {
                                    if (verse.Prostration.Type == ProstrationType.Recommended)
                                    {
                                        result.Add(verse);
                                    }
                                }
                            }
                        }
                        break;
                    case FindByProstrationType.Both:
                        {
                            foreach (Verse verse in source)
                            {
                                if (verse.Prostration != null)
                                {
                                    result.Add(verse);
                                }
                            }
                        }
                        break;
                }
            }
        }
        return result;
    }

    // find by revelation place
    public static List<Chapter> FindChapters(Book book, FindScope find_scope, Selection current_selection, List<Verse> previous_result, FindByRevelationPlace revelation_place)
    {
        return DoFindChapters(book, find_scope, current_selection, previous_result, revelation_place);
    }
    private static List<Chapter> DoFindChapters(Book book, FindScope find_scope, Selection current_selection, List<Verse> previous_result, FindByRevelationPlace revelation_place)
    {
        List<Verse> source = GetSourceVerses(book, find_scope, current_selection, previous_result);
        return DoFindChapters(source, revelation_place);
    }
    private static List<Chapter> DoFindChapters(List<Verse> source, FindByRevelationPlace revelation_place)
    {
        List<Chapter> result = new List<Chapter>();
        List<Verse> found_verses = new List<Verse>();
        if (source != null)
        {
            if (source.Count > 0)
            {
                Book book = source[0].Book;
                foreach (Verse verse in source)
                {
                    switch (revelation_place)
                    {
                        case FindByRevelationPlace.None:
                            {
                                // add nothing
                            }
                            break;
                        case FindByRevelationPlace.Makkah:
                            {
                                if (verse.Chapter != null)
                                {
                                    if (verse.Chapter.RevelationPlace == RevelationPlace.Makkah)
                                    {
                                        found_verses.Add(verse);
                                    }
                                }
                            }
                            break;
                        case FindByRevelationPlace.Medina:
                            {
                                if (verse.Chapter != null)
                                {
                                    if (verse.Chapter.RevelationPlace == RevelationPlace.Medina)
                                    {
                                        found_verses.Add(verse);
                                    }
                                }
                            }
                            break;
                        case FindByRevelationPlace.Both:
                            {
                                found_verses.Add(verse);
                            }
                            break;
                    }
                }
            }
        }

        int current_chapter_number = -1;
        foreach (Verse verse in found_verses)
        {
            if (verse.Chapter != null)
            {
                if (current_chapter_number != verse.Chapter.Number)
                {
                    current_chapter_number = verse.Chapter.Number;
                    result.Add(verse.Chapter);
                }
            }
        }
        return result;
    }

    public static string GetTranslationKey(Book book, string translation)
    {
        string result = "";
        if (book.TranslationInfos != null)
        {
            foreach (string key in book.TranslationInfos.Keys)
            {
                if (book.TranslationInfos[key].Name == translation)
                {
                    result = key;
                }
            }
        }
        return result;
    }
    public static void LoadTranslation(Book book, string translation)
    {
        Data.LoadTranslation(book, translation);
    }
    public static void UnloadTranslation(Book book, string translation)
    {
        Data.UnloadTranslation(book, translation);
    }
}
